﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Xml.Linq;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using System.Windows.Forms;
using AmaroK86.ImageFormat;
using AmaroK86.MassEffect3.ZlibBlock;
using AmaroK86.MassEffect3.PCCHandler;
//using AmaroK86.MassEffect3.TOCHandler;
using Gibbed.IO;
using Gibbed.MassEffect3.PackageUnpack;
using AmaroK86.MassEffect3.TexExplorer;

namespace AmaroK86.MassEffect3.Texture
{
    public partial class MainForm : Form
    {
        TextureDatabase texDB = new TextureDatabase();
        //string xmlTexDB = "texturesList.xml";
        string defaultPreviewName = "preview00.tga";

        public MainForm()
        {
            InitializeComponent();
            if (texDB.checkDBPaths(openFileDialog))
            {
                texDB.buildListFromDB(toolStripProgressBar);
                //texDB.newBuildList();
            }
            //treeView.Sort();
        }

        private void loadDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            /*string gamePath = @"C:\Program Files\Origin Games\Mass Effect 3\BIOGame\CookedPCConsole";
            //if the game isn't installed in the default folder
            if (!Directory.Exists(gamePath))
            {
                folderBrowserDialog.Description = "Please select the Mass Effect 3 game folder";
                do
                {
                    if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
                        gamePath = folderBrowserDialog.SelectedPath;
                    else
                        return;
                } while (!Directory.Exists(gamePath));
            }

            //if the xml database doesn't exist
            if (!File.Exists(xmlTexDB))
            {
                texDB.buildList();
            }
            else
                texDB.ReadFromXML(xmlTexDB);
            fillTreeView(null);*/

            fillTreeView(null);
        }

        private void fillTreeView(List<string> texIdList)
        {
            GC.Collect();
            TreeNode root,pccList,tfcOffsets;

            toolStripProgressBar.Visible = true;
            toolStripProgressBar.Minimum = 0;
            toolStripProgressBar.Value = 0;
            toolStripProgressBar.Step = 1;
            toolStripProgressBar.Maximum = texDB.Count;
            toolStripLabel.Visible = true;
            toolStripLabel.Text = "Building Tree List";
            this.Refresh();

            int countLoadedTex = 0;
            treeView.BeginUpdate();
            treeView.Nodes.Clear();
            foreach (TextureInfo texData in texDB.textureList)
            {
                toolStripProgressBar.PerformStep();

                // if check that controls if the texIdList has some value
                if (texIdList != null)
                {
                    if (texIdList.Count == 0)
                    {
                        toolStripProgressBar.Value = texDB.Count;
                        break; // exit from filling the list
                    }
                    if (texIdList.Contains(texData.id))
                        texIdList.Remove(texData.id);
                    else
                        continue; // skip to the next texture
                }
                countLoadedTex++;

                root = treeView.Nodes.Add(texData.id, texData.name);
                root.Nodes.Add("Size: " + texData.size);
                root.Nodes.Add("Original Size: " + texData.originalSize);
                root.Nodes.Add("Format: " + texData.format);
                root.Nodes.Add("MipTailBaseIdx: " + texData.mipTailBaseIdx);
                if (texData.internalFormatLODBias != 0)
                {
                    root.Nodes.Add("LOD Bias: " + texData.internalFormatLODBias);
                }
                root.Nodes.Add("LOD Group: " + texData.LODGroup + ((texData.LODGroupVal != 0) ? "_" + (texData.LODGroupVal - 1) : ""));
                root.Nodes.Add("Cache File: " + Path.GetFileName(texData.tfcFileName));
                if (texData.compressionSettings != "")
                    root.Nodes.Add("Texture Type: " + texData.compressionSettings.Substring(3));
                pccList = root.Nodes.Add("pcc Files: ");
                foreach (KeyValuePair<string, long> kvp in texData.pccFileList)
                {
                    pccList.Nodes.Add(kvp.Key + " with offset 0x" + kvp.Value.ToString("X8"));
                }
                if (texData.TexDataList.Count > 0)
                {
                    tfcOffsets = root.Nodes.Add("TFC Texture Offsets: ");
                    foreach (TextureData texTFC in texData.TexDataList)
                    {
                        TreeNode tfcTexNode = new TreeNode();
                        tfcTexNode = tfcOffsets.Nodes.Add(texData.id + "=" + texTFC.id, "Size: " + texTFC.texSize + " at offset 0x" + texTFC.arcOffset.ToString("X8"));
                        //tfcTexNode.Name = texData.id + "=" + texTFC.texSize;
                    }
                }
                root.Name = texData.id + "=" + texDB.getMaxImg(texData.id);
            }

            treeView.EndUpdate();
            toolStripLabel.Text = "Done. Loaded " + countLoadedTex + " Textures.";
            toolStripProgressBar.Visible = false;
        }

        private void updateTreeNode(string texId, string imgId)
        {
            treeView.BeginUpdate();

            TreeNode root, pccList, tfcOffsets;
            string oldKey = texId + "=" + imgId;
            string newKey = texId + "=" + texDB.getMaxImg(texId);
            if (treeView.Nodes.ContainsKey(oldKey))
                root = treeView.Nodes[oldKey];
            else if (treeView.Nodes.ContainsKey(newKey))
            {
                root = treeView.Nodes[newKey];
                root.Name = newKey;
            }
            else
            {
                MessageBox.Show("index " + oldKey + " not found");
                treeView.EndUpdate();
                return;
            }

            TextureInfo texData = texDB.textureList[texId];

            root.Nodes.Clear();
            root.Nodes.Add("Size: " + texData.size);
            root.Nodes.Add("Original Size: " + texData.originalSize);
            root.Nodes.Add("Format: " + texData.format);
            root.Nodes.Add("MipTailBaseIdx: " + texData.mipTailBaseIdx);
            if (texData.internalFormatLODBias != 0)
            {
                root.Nodes.Add("LOD Bias: " + texData.internalFormatLODBias);
            }
            root.Nodes.Add("LOD Group: " + texData.LODGroup + ((texData.LODGroupVal != 0) ? "_" + (texData.LODGroupVal-1) : ""));
            root.Nodes.Add("Cache File: " + Path.GetFileName(texData.tfcFileName));
            if (texData.compressionSettings != "")
                root.Nodes.Add("Texture Type: " + texData.compressionSettings.Substring(3));
            pccList = root.Nodes.Add("pcc Files: ");
            foreach (KeyValuePair<string, long> kvp in texData.pccFileList)
            {
                pccList.Nodes.Add(kvp.Key + " with offset 0x" + kvp.Value.ToString("X8"));
            }

            if (texData.TexDataList.Count > 0)
            {
                tfcOffsets = root.Nodes.Add("TFC Texture Offsets: ");
                foreach (TextureData texTFC in texData.TexDataList)
                {
                    TreeNode tfcTexNode = new TreeNode();
                    tfcTexNode = tfcOffsets.Nodes.Add(texData.id + "=" + texTFC.id, "Size: " + texTFC.texSize + " at offset 0x" + texTFC.arcOffset.ToString("X8"));
                }
            }
            treeView.EndUpdate();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            //if the xml doesn't exist, creates it
            //if (texDB.textureList != null)// && !File.Exists(xmlTexDB))
            //    texDB.WriteToXML(xmlTexDB);

            //fix automatically toc.bin file
            TOCHandler tocH = new TOCHandler(@"E:\Mass Effect 3\BIOGame\PCConsoleTOC.bin");
            tocH.fixSizes();

            //deleting preview image
            if (File.Exists(defaultPreviewName))
                File.Delete(defaultPreviewName);
        }

        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            Bitmap previewImage = null;
            string previewFileName = "";
            TextureInfo textureInfo;

            if (treeView.SelectedNode.Name == null || treeView.SelectedNode.Name == "")
                return;

            string[] values = treeView.SelectedNode.Name.Split('=');
            string texId = values[0];
            string imgId = values[1];

            try
            {
                textureInfo = texDB.textureList[texId];
            }
            catch
            {
                return;
            }
            if (textureInfo.TexDataList[imgId].arcOffset == -1)
                return;

            float tfcSizeX = (float)textureInfo.TexDataList[imgId].texSize.width;
            float tfcSizeY = (float)textureInfo.TexDataList[imgId].texSize.height;
            float maxSize = 512;
            float redux = (float)Math.Max(tfcSizeX,tfcSizeY) / maxSize;
            float sizeX, sizeY;

            sizeX = textureInfo.TexDataList[imgId].texSize.width / redux;
            sizeY = textureInfo.TexDataList[imgId].texSize.height / redux;

            switch (textureInfo.format)
            {
                case "V8U8":
                case "DXT1":
                case "DXT5":
                    previewFileName = "preview.dds";
                    extractImage(texId, imgId, previewFileName);
                    ExecuteCommandSync("readdxt " + previewFileName);
                    File.Delete(previewFileName);
                    previewFileName = defaultPreviewName;
                    break;
                case "G8":
                case "A8R8G8B8":
                    previewFileName = defaultPreviewName;
                    extractImage(texId, imgId, previewFileName);
                    break;
            }

            if (previewImage != null)
            {
                previewImage.Dispose();
            }
            // Stretches the image to fit the pictureBox.
            if (File.Exists(previewFileName))
            {
                if(pictureBox.Image != null)
                    pictureBox.Image.Dispose();
                pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
                pictureBox.ClientSize = new Size((int)sizeX, (int)sizeY);
                pictureBox.Image = Paloma.TargaImage.LoadTargaImage(previewFileName);
            }
            GC.Collect();
        }

        public void extractImage(string texId, string imgId, string outputFileName = "")
        {
            if (!texDB.textureList[texId].TexDataList.Contains(imgId))
                return;

            texDB.extractImage(texId, imgId, outputFileName);
        }

        public void ExecuteCommandSync(object command)
        {
            try
            {
                // create the ProcessStartInfo using "cmd" as the program to be run,
                // and "/c " as the parameters.
                // Incidentally, /c tells cmd that we want it to execute the command that follows,
                // and then exit.
                System.Diagnostics.ProcessStartInfo procStartInfo =
                    new System.Diagnostics.ProcessStartInfo("cmd", "/c " + command);

                // The following commands are needed to redirect the standard output.
                // This means that it will be redirected to the Process.StandardOutput StreamReader.
                procStartInfo.RedirectStandardOutput = true;
                procStartInfo.UseShellExecute = false;
                // Do not create the black window.
                procStartInfo.CreateNoWindow = true;
                // Now we create a process, assign its ProcessStartInfo and start it
                System.Diagnostics.Process proc = new System.Diagnostics.Process();
                proc.StartInfo = procStartInfo;
                proc.Start();
                // Get the output into a string
                string result = proc.StandardOutput.ReadToEnd();
                // Display the command output.
                Console.WriteLine(result);
            }
            catch
            {
                // Log the exception
            }
        }

/*        private void readXMLData(string XMLFileName)
        {
            XDocument xdoc = XDocument.Load(XMLFileName);
            var element = xdoc.Elements("gamePath").Single();
            element.Value = "foo";
            xdoc.Save("file.xml");
        }

        private void writeXMLData(string XMLFileName)
        {
            XDocument xdoc = XDocument.Load(XMLFileName);
            var element = xdoc.Elements("gamePath").Single();
            element.Value = "foo";
            xdoc.Save("file.xml");
        }*/

        private void extractTextureToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TreeNode node = treeView.SelectedNode;
            if (node == null)
                return;

            string[] values = treeView.SelectedNode.Name.Split('=');

            if (values.Length != 2)
                return;

            string texId = values[0];
            string imgId = values[1];
            string texName;

            TextureInfo texInfo = texDB.textureList[texId];
            TextureData texData = texInfo.TexDataList[imgId];

            string texFormat = "";
            string filter = "All files|*.*";

            switch (texInfo.format)
            {
                case "DXT1":
                case "DXT5":
                    filter = "DDS image|*.dds|" + filter;
                    texName = texInfo.name + "_" + texData.texSize;
                    texFormat = ".dds";
                    break;
                case "G8":
                case "V8U8":
                case "A8R8G8B8":
                    filter = "TGA image|*.tga|" + filter;
                    texName = texInfo.name + "_" + texData.texSize;
                    texFormat = ".tga";
                    break;
                default:
                    texName = "unknown";
                    texFormat = ".bin";
                    break;
            }

            saveFileDialog.FileName = texName + texFormat;
            saveFileDialog.Filter = filter;
            saveFileDialog.DefaultExt = texFormat;
            if (saveFileDialog.ShowDialog() == DialogResult.OK)
                extractImage(texId, imgId, saveFileDialog.FileName);
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void replaceTextureToolStripMenuItem_Click(object sender, EventArgs e)
        {
            TreeNode node = treeView.SelectedNode;
            if (node == null)
                return;

            string[] values = treeView.SelectedNode.Name.Split('=');

            if (values.Length != 2)
                return;

            string texId = values[0];
            string imgId = values[1];

            openFileDialog.Filter = "Image Files(*.dds;*.tga)|*.dds;*.tga|All files (*.*)|*.*";
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                if (File.Exists(openFileDialog.FileName))
                {
                    texDB.replaceImage(texId, imgId, openFileDialog.FileName);
                }
            }

            MessageBox.Show("Texture replaced successfully!");
            updateTreeNode(texId, imgId);
            //fillTreeView(null);
            //GC.Collect();
            GC.WaitForPendingFinalizers();
        }

        private void backgroundWorkerFillTreeView_DoWork(object sender, DoWorkEventArgs e)
        {
        }

        private void rebuildDatabaseToolStripMenuItem_Click(object sender, EventArgs e)
        {
            DialogResult dialogResult = MessageBox.Show("Warning! This is a very slow and cpu intensive procedure, the program may take at least 10 min to complete, do you want to procede?", "Warning", MessageBoxButtons.YesNo, MessageBoxIcon.Warning);
            if (dialogResult == DialogResult.Yes)
            {
                texDB.buildList(toolStripProgressBar,toolStripLabel);
            }
        }

        private void byTagToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string query = "SELECT Name AS Result FROM tbl_Tag ORDER BY Name";

            FilterForm filterForm = new FilterForm(texDB.sqlDB.GetDataTable(query));
            filterForm.ShowDialog();
            filterForm.Dispose();

            if (filterForm.filter)
            {
                string queryTex = "SELECT DISTINCT Tex.Id FROM tbl_Texture AS Tex, tbl_Tex_Tag AS Tex_Tag, tbl_Tag AS Tag WHERE Tex.Id = Tex_Tag.Tex_Id AND Tag.Id = Tex_Tag.Tag_Id AND (";// Pcc.File_Name LIKE '%EntryMenu.pcc'";
                CheckedListBox result = filterForm.getListBox();
                if (result.CheckedItems.Count == 0)
                    return;
                foreach (Object entry in result.CheckedItems)
                {
                    queryTex += "Tag.Name LIKE '%" + entry.ToString() + "' OR ";
                }
                queryTex = queryTex.Substring(0, queryTex.Length - 4); // get rid of last unused OR
                queryTex += ");";
                DataTable tableTex = texDB.sqlDB.GetDataTable(queryTex);
                List<string> texIdList = new List<string>();

                foreach (DataRow texRow in tableTex.Rows)
                {
                    texIdList.Add(Convert.ToString(texRow["Id"]));
                }
                fillTreeView(texIdList);
            }
        }

        private void resetFilterToolStripMenuItem_Click(object sender, EventArgs e)
        {
            fillTreeView(null);
        }

        private void byPccFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string query = "SELECT File_Name AS Result FROM tbl_Pcc ORDER BY File_Name";

            FilterForm filterForm = new FilterForm(texDB.sqlDB.GetDataTable(query));
            filterForm.ShowDialog();
            filterForm.Dispose();

            if (filterForm.filter)
            {
                string queryTex = "SELECT DISTINCT Tex.Id FROM tbl_Texture AS Tex, tbl_Tex_Pcc AS Tex_Pcc, tbl_Pcc AS Pcc WHERE Tex.Id = Tex_Pcc.Tex_Id AND Pcc.Id = Tex_Pcc.Pcc_Id AND (";// Pcc.File_Name LIKE '%EntryMenu.pcc'";
                CheckedListBox result = filterForm.getListBox();
                if (result.CheckedItems.Count == 0)
                    return;
                foreach (Object entry in result.CheckedItems)
                {
                    queryTex += "Pcc.File_Name LIKE '%" + entry.ToString() + "' OR ";
                }
                queryTex = queryTex.Substring(0, queryTex.Length - 4);
                queryTex += ");";
                DataTable tableTex = texDB.sqlDB.GetDataTable(queryTex);
                List<string> texIdList = new List<string>();

                foreach (DataRow texRow in tableTex.Rows)
                {
                    texIdList.Add(Convert.ToString(texRow["Id"]));
                }
                fillTreeView(texIdList);
            }
        }

        private void treeView_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Button.Equals(MouseButtons.Right))
            {
                treeView.SelectedNode = e.Node;
                contextMenuStrip.Show(MousePosition);
            }
        }

        private void addTagToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (treeView.SelectedNode == null)
                return;
            string texId = treeView.SelectedNode.Name.Split('=')[0];
            string query = "SELECT Name AS Result FROM tbl_Tag WHERE Id NOT IN (SELECT DISTINCT Tag_Id FROM tbl_Tex_Tag WHERE Tex_Id = '" + texId + "') ORDER BY Name";

            FilterForm filterForm = new FilterForm(texDB.sqlDB.GetDataTable(query));
            filterForm.ShowDialog();
            filterForm.Dispose();

            if (filterForm.filter)
            {
                List<Dictionary<String, String>> insertTexTagList = new List<Dictionary<string, string>>();
                CheckedListBox result = filterForm.getListBox();
                foreach (Object entry in result.CheckedItems)
                {
                    Dictionary<String, String> dataSQL = new Dictionary<String, String>();
                    dataSQL.Add("Tex_Id", texId);
                    dataSQL.Add("Tag_Id", texDB.sqlDB.ExecuteScalar("SELECT Id FROM tbl_Tag WHERE Name = '" + entry.ToString() + "' LIMIT 1;"));
                    insertTexTagList.Add(dataSQL);
                }
                texDB.sqlDB.InsertTransaction("tbl_Tex_Tag", insertTexTagList);
            }
        }

        private void byTfcFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            string query = "SELECT File_Name AS Result FROM tbl_Tfc ORDER BY File_Name";

            FilterForm filterForm = new FilterForm(texDB.sqlDB.GetDataTable(query));
            filterForm.ShowDialog();
            filterForm.Dispose();

            if (filterForm.filter)
            {
                string queryTex = "SELECT Tex.Id FROM tbl_Texture AS Tex, tbl_Tfc AS Tfc WHERE Tex.Tfc_Id = Tfc.Id AND (";// Pcc.File_Name LIKE '%EntryMenu.pcc'";
                CheckedListBox result = filterForm.getListBox();
                if (result.CheckedItems.Count == 0)
                    return;
                foreach (Object entry in result.CheckedItems)
                {
                    queryTex += "Tfc.File_Name LIKE '%" + entry.ToString() + "' OR ";
                }
                queryTex = queryTex.Substring(0, queryTex.Length - 4);
                queryTex += ");";
                DataTable tableTex = texDB.sqlDB.GetDataTable(queryTex);
                List<string> texIdList = new List<string>();

                foreach (DataRow texRow in tableTex.Rows)
                {
                    texIdList.Add(Convert.ToString(texRow["Id"]));
                }
                fillTreeView(texIdList);
            }
        }

        private void addNewTagToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SearchAddForm searchAddForm = new SearchAddForm();
            searchAddForm.Text = "Insert name of the new tag";
            searchAddForm.btnOkText = "Add";
            searchAddForm.ShowDialog();
            searchAddForm.Dispose();

            if (searchAddForm.searchAdd)
            {
                try
                {
                    Dictionary<string, string> insertTag = new Dictionary<string, string>();
                    insertTag.Add("Name", searchAddForm.result);
                    texDB.sqlDB.Insert("tbl_Tag", insertTag);
                }
                catch(Exception exc)
                {
                    MessageBox.Show("An error occurred while adding Tag: " + exc.Message,"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void removeTagToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }

        private void byTextureNameToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SearchAddForm searchAddForm = new SearchAddForm();
            searchAddForm.Text = "Insert name of the Texture";
            searchAddForm.btnOkText = "Search";
            searchAddForm.ShowDialog();
            searchAddForm.Dispose();

            if (searchAddForm.searchAdd)
            {
                string queryTexName = "SELECT Tex.Id FROM tbl_Texture AS Tex WHERE Tex.NAME LIKE '%" + searchAddForm.result + "%';";
                DataTable tableTex = texDB.sqlDB.GetDataTable(queryTexName);
                List<string> texIdList = new List<string>();

                foreach (DataRow texRow in tableTex.Rows)
                {
                    texIdList.Add(Convert.ToString(texRow["Id"]));
                }
                fillTreeView(texIdList);
            }
        }
    }
}
